using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;

namespace GPU_Noise
{
    static public class Util
    {
        static Random generator = new Random();
        public static float RandomFloat(float start, float end)
        {
            return start + ((end - start) * (float)generator.NextDouble());
        }
    }

    public static class Camera
    {
        static Matrix vp;
        static Matrix view;
        static Matrix proj;
        static Vector3 pos = Vector3.Zero;
        static Vector2 ang = Vector2.Zero;
        static Vector3 lastForward = Vector3.Forward;
        static Vector2 lastMouse = Vector2.Zero;

        public static Matrix ViewProjection
        {
            get
            {
                return vp;
            }
        }

        public static Matrix View
        {
            get
            {
                return view;
            }
        }


        public static Matrix Proj
        {
            get
            {
                return proj;
            }
        }

        public static Matrix InverseView
        {
            get
            {
                return Matrix.Invert(view);
            }
        }

        public static void Initalize(Vector3 position, Vector3 direction)
        {
            pos = position;
            lastForward = direction;


        }

        public static void Update()
        {
            Vector3 v = Vector3.Zero;

            KeyboardState ks = Keyboard.GetState();
            MouseState ms = Mouse.GetState();

            if (ks.IsKeyDown(Keys.W) || ks.IsKeyDown(Keys.Up))
                v.Z += 0.001f;
            if (ks.IsKeyDown(Keys.S) || ks.IsKeyDown(Keys.Down))
                v.Z -= 0.001f;
            if (ks.IsKeyDown(Keys.A) || ks.IsKeyDown(Keys.Left))
                v.X -= 0.001f;
            if (ks.IsKeyDown(Keys.D) || ks.IsKeyDown(Keys.Right))
                v.X += 0.001f;

            if (ks.IsKeyDown(Keys.LeftShift) || ks.IsKeyDown(Keys.RightShift))
                v *= 5;

            Vector2 mouse = new Vector2(-ms.X, ms.Y);
            Vector2 newAng = ang - (mouse - lastMouse) * 0.01f;

            lastMouse = mouse;

            Vector3 right = new Vector3((float)Math.Sin((double)-newAng.X), 0f, (float)Math.Cos((double)-newAng.X));
            Vector3 newForward = Vector3.Cross(Vector3.Up, right);

            newForward = Vector3.TransformNormal(newForward, Matrix.CreateFromAxisAngle(right, newAng.Y));
            newForward.Normalize();
            ang.X = newAng.X;
            if (Math.Acos(Vector3.Dot(newForward, Vector3.Normalize(new Vector3(newForward.X, 0f, newForward.Z)))) < MathHelper.ToRadians(85.0f))
            {
                lastForward = newForward;
                ang.Y = newAng.Y;
            }

            right.Normalize();

            pos += (lastForward * v.Z) + (right * v.X);

            view = Matrix.CreateLookAt(pos, pos + lastForward, Vector3.Up);
            proj = Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(70f), 4f / 3f, 0.1f, 100f);

            vp = view * proj;
        }
    }

    public class Demo : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;
        SpriteFont demofont;
        class Cloud
        {
            Matrix world = Matrix.Identity;
            public void Create(GraphicsDevice GraphicsDevice, ContentManager Content)
            {
                const int COUNT = 15;
                demoVertices = new VertexPositionNormalTexture[COUNT * 6];
                const float SPREAD = 1f;
                const float CLOUD_SPREAD = 10f;
                const float RADIUS = 5f;
                for (int i = 0; i < COUNT; i++)
                {
                    Vector3 offset = new Vector3(0, 0, SPREAD * (float)i / (float)COUNT);


                    demoVertices[6 * i + 0] = new VertexPositionNormalTexture(offset + new Vector3(-RADIUS, RADIUS, 0),
                        Vector3.One, new Vector2(0, 1));
                    demoVertices[6 * i + 1] = new VertexPositionNormalTexture(offset + new Vector3(RADIUS, RADIUS, 0),
                        Vector3.One, new Vector2(1, 1));
                    demoVertices[6 * i + 2] = new VertexPositionNormalTexture(offset + new Vector3(RADIUS, -RADIUS, 0),
                        Vector3.One, new Vector2(1, 0));
                    demoVertices[6 * i + 3] = new VertexPositionNormalTexture(offset + new Vector3(-RADIUS, RADIUS, 0),
                        Vector3.One, new Vector2(0, 1));
                    demoVertices[6 * i + 4] = new VertexPositionNormalTexture(offset + new Vector3(RADIUS, -RADIUS, 0),
                        Vector3.One, new Vector2(1, 0));
                    demoVertices[6 * i + 5] = new VertexPositionNormalTexture(offset + new Vector3(-RADIUS, -RADIUS, 0),
                        Vector3.One, new Vector2(0, 0));
                }

                world = Matrix.CreateTranslation(Util.RandomFloat(-CLOUD_SPREAD, CLOUD_SPREAD), Util.RandomFloat(-CLOUD_SPREAD, CLOUD_SPREAD), Util.RandomFloat(-CLOUD_SPREAD, CLOUD_SPREAD));
                demoVB = new VertexBuffer(GraphicsDevice, VertexPositionNormalTexture.SizeInBytes * demoVertices.Length, BufferUsage.None);
                demoVB.SetData<VertexPositionNormalTexture>(demoVertices);
                demoDeclaration = new VertexDeclaration(GraphicsDevice, VertexPositionNormalTexture.VertexElements);
            }

            public void Draw(GraphicsDevice GraphicsDevice, Effect demoShader)
            {
                GraphicsDevice.VertexDeclaration = demoDeclaration;
                GraphicsDevice.Vertices[0].SetSource(demoVB, 0, VertexPositionNormalTexture.SizeInBytes);


                demoShader.Parameters["ViewProjection"].SetValue(Camera.ViewProjection);
                demoShader.Parameters["World"].SetValue(world);


                demoShader.Begin();


                foreach (EffectPass pass in demoShader.CurrentTechnique.Passes)
                {
                    pass.Begin();
                    GraphicsDevice.DrawPrimitives(PrimitiveType.TriangleList, 0, demoVertices.Length / 3);
                    pass.End();
                }

                demoShader.End();
            }

            VertexDeclaration demoDeclaration;
            VertexBuffer demoVB;
            VertexPositionNormalTexture[] demoVertices;
        }

        uint framesCount = 0;
        uint fps = 0;
        int lastSecond = 0;
        const int CLOUD_COUNT = 1;
        Cloud[] clouds = new Cloud[CLOUD_COUNT];
        Effect demoShader;
        const string APP_TITLE = "GPU Noise Demo";

        Texture2D baseTexture;
        Texture2D maskProfileTexture;
        float noisePhase;

        public Demo()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
        }

        protected override void Initialize()
        {
            base.Initialize();
            Window.Title = APP_TITLE;
            Camera.Initalize(new Vector3(0, 0, -1), Vector3.Forward);
        }

        void GenerateBaseData()
        {
            Color[] baseTextureData = new Color[16];
            for (uint x = 0; x < 8; x++)
            {
                Vector3 v = new Vector3(Util.RandomFloat(0f, 1f), Util.RandomFloat(0f, 1f), Util.RandomFloat(0f, 1f));
                v.Normalize();
                baseTextureData[x] = new Color(v);
            }
            for (uint x = 0; x < 8; x++)
            {
                float r = Util.RandomFloat(0f, 1f);
                Vector3 v = new Vector3(r, r, r);
                baseTextureData[x + 8] = new Color(v);
            }

            if (baseTexture != null)
                baseTexture.Dispose();

            baseTexture = new Texture2D(GraphicsDevice, 8, 2);
            baseTexture.SetData<Color>(baseTextureData);
        }

        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        Vector3 ModelPosition;
        float ModelRotation = 0.0f;
        Model Model;
        Model SkySphere;
        Effect SkySphereEffect;
        Matrix projectionMatrix;
        protected void LoadSkyContent()
        {
            Model = Content.Load<Model>("redtorus");
            ModelPosition = Vector3.Zero;
            projectionMatrix = Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, 4.0f / 3.0f, 1.0f, 10000f);
            // Load the effect, the texture it uses, and 
            // the model used for drawing it
            SkySphereEffect = Content.Load<Effect>("SkySphere");
            TextureCube SkyboxTexture = Content.Load<TextureCube>("uffizi_cross");
            SkySphere = Content.Load<Model>("SphereHighPoly");

            // Set the parameters of the effect
            //SkySphereEffect.Parameters["ViewMatrix"].SetValue(myCamera2.ViewMatrix);
            SkySphereEffect.Parameters["ViewMatrix"].SetValue(Camera.View);
            SkySphereEffect.Parameters["ProjectionMatrix"].SetValue(Camera.Proj);
            SkySphereEffect.Parameters["SkyboxTexture"].SetValue(SkyboxTexture);
            // Set the Skysphere Effect to each part of the Skysphere model
            foreach (ModelMesh mesh in SkySphere.Meshes)
            {
                foreach (ModelMeshPart part in mesh.MeshParts)
                {
                    part.Effect = SkySphereEffect;
                }
            }

        }
        protected override void LoadContent()
        {
            spriteBatch = new SpriteBatch(GraphicsDevice);
            demoShader = Content.Load<Effect>("Noise");
            maskProfileTexture = Content.Load<Texture2D>("MaskProfile");
            demofont = Content.Load<SpriteFont>("font");


            GenerateBaseData();

            for (uint i = 0; i < CLOUD_COUNT; i++)
            {
                clouds[i] = new Cloud();
                clouds[i].Create(GraphicsDevice, Content);
            }
            LoadSkyContent();
        }

        protected override void UnloadContent()
        {
        }

        protected override void Update(GameTime gameTime)
        {
            if (Keyboard.GetState().IsKeyDown(Keys.Space))
                GenerateBaseData();

            base.Update(gameTime);
        }


        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected void DrawSky(GameTime gameTime)
        {

            //Draw the model, a model can have multiple meshes, so loop
            foreach (ModelMesh mesh in Model.Meshes)
            {
                //This is where the mesh orientation is set, as well as our camera and projection
                foreach (BasicEffect effect in mesh.Effects)
                {
                    effect.EnableDefaultLighting();
                    effect.World = Matrix.Identity * Matrix.CreateRotationY(ModelRotation)
                        * Matrix.CreateTranslation(ModelPosition);
                    //effect.View = myCamera.View;
                    effect.View = Camera.View;
                    effect.Projection = Camera.Proj;
                }
                //Draw the mesh, will use the effects set above.
                mesh.Draw();
            }

            // Set the View and Projection matrix for the effect
            SkySphereEffect.Parameters["ViewMatrix"].SetValue(Camera.View);
            //SkySphereEffect.Parameters["ViewMatrix"].SetValue(myCamera.View);
            SkySphereEffect.Parameters["ProjectionMatrix"].SetValue(Camera.Proj);
            // Draw the sphere model that the effect projects onto
            foreach (ModelMesh mesh in SkySphere.Meshes)
            {
                mesh.Draw();
            }

            // Undo the renderstate settings from the shader
            graphics.GraphicsDevice.RenderState.CullMode = CullMode.CullCounterClockwiseFace;
            graphics.GraphicsDevice.RenderState.DepthBufferWriteEnable = true;
            base.Draw(gameTime);
        }

        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(new Color(105, 125, 130));

            Camera.Update();
            DrawSky(gameTime);
            noisePhase += 0.001f;

            if (gameTime.TotalRealTime.Seconds > lastSecond)
            {
                lastSecond = gameTime.TotalRealTime.Seconds;
                fps = framesCount;
                framesCount = 0;
            }
            else
            {
                framesCount++;
            }

            GraphicsDevice.RenderState.CullMode = CullMode.None;

            GraphicsDevice.RenderState.PointSpriteEnable = true;
            GraphicsDevice.RenderState.DepthBufferWriteEnable = false;
            GraphicsDevice.RenderState.SourceBlend = Blend.SourceAlpha;
            GraphicsDevice.RenderState.DestinationBlend = Blend.InverseSourceAlpha;

            GraphicsDevice.SamplerStates[0].MinFilter = TextureFilter.Point;
            GraphicsDevice.SamplerStates[0].MagFilter = TextureFilter.Point;
            GraphicsDevice.SamplerStates[0].MipFilter = TextureFilter.Point;
            GraphicsDevice.SamplerStates[0].AddressU = TextureAddressMode.Wrap;
            GraphicsDevice.SamplerStates[0].AddressV = TextureAddressMode.Wrap;
            GraphicsDevice.Textures[0] = baseTexture;

            GraphicsDevice.SamplerStates[2].AddressU = TextureAddressMode.Wrap;
            GraphicsDevice.SamplerStates[2].AddressV = TextureAddressMode.Wrap;
            GraphicsDevice.Textures[2] = maskProfileTexture;

            demoShader.Parameters["Time"].SetValue(noisePhase);
            demoShader.Parameters["View"].SetValue(Camera.View);
            demoShader.Parameters["Proj"].SetValue(Camera.Proj);
            demoShader.Parameters["InvView"].SetValue(Camera.InverseView);
            demoShader.Parameters["InvProj"].SetValue(Matrix.Invert(Camera.Proj));
            demoShader.Parameters["InvViewProj"].SetValue(Matrix.Invert(Camera.View * Camera.Proj));

            for (uint i = 0; i < CLOUD_COUNT; i++)
            {
                clouds[i].Draw(GraphicsDevice, demoShader);
            }

            spriteBatch.Begin();
            spriteBatch.DrawString(demofont, APP_TITLE + " (3D Perlin noise on CPU)", Vector2.One * 10, Color.Black);

            spriteBatch.DrawString(demofont, "Base data (Row one: Gradients, Row two: Permutations)", new Vector2(10, 35), Color.Black, 0f, Vector2.Zero, 0.75f, SpriteEffects.None, 0f);
            spriteBatch.Draw(baseTexture, new Vector2(10, 50), null, Color.White, 0f, Vector2.Zero, 20f, SpriteEffects.None, 0f);

            spriteBatch.DrawString(demofont, "Hardcoded Octaves (Shader)", new Vector2(10, 100), Color.Black, 0f, Vector2.Zero, 0.75f, SpriteEffects.None, 0f);
            spriteBatch.DrawString(demofont, "Noise phase: " + (Math.Round(noisePhase - (int)noisePhase, 2)), new Vector2(10, 110), Color.Black, 0f, Vector2.Zero, 0.75f, SpriteEffects.None, 0f);
            spriteBatch.DrawString(demofont, "FPS: " + fps, new Vector2(10, 120), Color.Black, 0f, Vector2.Zero, 0.75f, SpriteEffects.None, 0f);
            spriteBatch.DrawString(demofont, "Dacre Denny 23/06/09", new Vector2(10, 130), Color.Black, 0f, Vector2.Zero, 0.75f, SpriteEffects.None, 0f);
            spriteBatch.DrawString(demofont, "www.dacredenny.com", new Vector2(10, 140), Color.Black, 0f, Vector2.Zero, 0.75f, SpriteEffects.None, 0f);
            spriteBatch.End();

            base.Draw(gameTime);
        }
    }
}
